#!/usr/bin/env bash
#Frame to Wallpaper by YonatanBaum and noxonad

# Option defaults
FRAME_LOCATION="."
KEEP_FRAME=0
VERBOSITY=0
APP_NAME="FtW (Frame to Wallpaper)"
VERSION=1.0.1

usage() { echo "Usage: $(basename $0) [OPTIONS] SOURCE_VIDEO [,SOURCE_VIDEO]"; }

help() {
  usage
  echo "
  Selects a random video from specified videos, then selects a random frame from that
  video, and sets it as the desktop environment wallpaper.

  OPTIONS:
    -h, --help                   Display this text.
    -v                           Enable verbose output. Stacks up to 3 (-vvv).
    -k, --keep-frame             Keep the frame as PNG instead of removing it after usage.
    --frame-location=LOCATION    Frame's file location. Defaults to current folder.             
    --version                    Displays the version
  "
  exit 2
}

version() {
  echo "$APP_NAME $VERSION"
}

print_if_verbose() {
  local verbosity_level=$1
  local message=$2

  if [[ $VERBOSITY -ge $verbosity_level ]]
  then
    echo $message >& 2
  fi
}

# checks for help and version opts
INPUT=$1
if [[ $INPUT = "-h" || $INPUT = "--help" ]]
then
	help
	exit 2
fi

if [[ $INPUT = "--version" ]]
then
  version
  exit 3
fi

check_file() {
  if [[ ! -f $1 ]]
  then
    >&2 echo "ERROR: File $1 does not exist."
    exit
  fi
}

# Parse command line arguments
while getopts ":hvk-:" arg
do
  case $arg in
    h)
    help
    ;;
    v)
    VERBOSITY=$((VERBOSITY + 1))
    ;;
    k)
    KEEP_FRAME=1
    ;;
    -) 
      case "${OPTARG}" in
        help)
        help
        ;;
        version)
        version
        ;;
        keep-frame)
        KEEP_FRAME=1
        ;;
        frame-location=*)
        FRAME_LOCATION=${OPTARG#*=}
        FRAME_LOCATION=${FRAME_LOCATION/#~/$HOME}
        if [[ ! -d FRAME_LOCATION ]]; then
          2&> echo "ERROR: $FRAME_LOCATION is not a directory."
        fi
        ;;
      esac
  esac
done

shift $(($OPTIND - 1))
VIDEOS=( $@ )
VIDEO=${VIDEOS[$(shuf -i 0-$(expr ${#VIDEOS[@]} - 1) -n 1)]}
check_file $VIDEO

get_metadata() {
  echo $(ffprobe -select_streams v -show_streams $1 2> /dev/null)
}

get_total_frames() {
  echo $(echo $@ | sed 's|\s|\n|g' | grep nb_frames | cut -d"=" -f 2)
}

get_framerate() {
  echo $(echo $@ | sed 's|\s|\n|g' | grep avg_frame_rate | cut -d"=" -f 2)
}

get_duration() {
  echo $(echo $@ | sed 's|\s|\n|g' | grep 'duration=' | cut -d"=" -f 2)
}

get_duration_by_tag() {
  echo $(echo $@ | sed 's|\s|\n|g' | grep 'DURATION=' | cut -d"=" -f 2)
}

frame_to_timestamp() {
  FRAME=$1
  FRAMERATE=$(echo "$2" | bc)
  
  print_if_verbose 3 "Using framerate $FRAMERATE"

  SECONDS=$(( ($FRAME / $FRAMERATE / 1) % 60))
  MINUTES=$((( ($FRAME / $FRAMERATE) / 60) % 60))
  HOURS=$((( ($FRAME / $FRAMERATE) / 3600) % 60))
  MILLISECONDS=$(echo "$FRAME % 60" | bc)
  if [[ $MILLISECONDS -eq 0 ]]
  then
    MILLISECONDS=0
  else
    MILLISECONDS=$(echo "1 / $MILLISECONDS * 1000000" | bc -l | cut -d"." -f 1)
  fi
  
  TIMESTAMP=$(printf "%02d:%02d:%02d.%05d" $HOURS $MINUTES $SECONDS $MILLISECONDS)
  echo $TIMESTAMP
}

timestamp_to_frame() {
  TIMESTAMP=$1
  FRAMERATE=$2

  HOURS=$(echo $TIMESTAMP | cut -d":" -f 1)
  MINUTES=$(echo $TIMESTAMP | cut -d":" -f 2)
  SECONDS=$(echo $TIMESTAMP | cut -d"." -f 1 | cut -d":" -f 3)
  MILLISECONDS=$(echo $TIMESTAMP | cut -d"." -f 2)

  FRAME=$(echo "($HOURS * 3600000 + $MINUTES * 60000 + $SECONDS * 1000 + ($MILLISECONDS % 60) ) / 1000" | bc)

  echo $FRAME
}

get_timestamp() {
  local VIDEO=$1
  METADATA=$(get_metadata $VIDEO)
  FRAMES=$(get_total_frames $METADATA)
  FRAMERATE=$(get_framerate $METADATA)
  if [[ "$FRAMES" != "N/A" ]] # If number of frames is in metadata of the time
  then
    # Choose random frame.
    FRAME=$(shuf -i 1-$FRAMES -n 1)
    TIMESTAMP=$(frame_to_timestamp $FRAME $FRAMERATE)
    print_if_verbose 3 "$VIDEO contains $FRAMES frame(s) at the rate of $FRAMERATE FPS."

    print_if_verbose 3 "Selected frame #$FRAME at timestamp $TIMESTAMP"

    echo $TIMESTAMP
    return 0
  fi

  print_if_verbose 1 "$VIDEO does not have frame metadata. Trying a backup plan"

  FRAMES=$(get_duration $METADATA)
  if [[ "$FRAMES" == "N/A" ]]
  then
    print_if_verbose 1 "$VIDEO does not have duration metadata. Trying get duration by tag."
    DURATION=$(get_duration_by_tag $METADATA)
    FRAMES=$(timestamp_to_frame $DURATION $FRAMERATE)
  fi

  FRAMES=$(echo "$FRAMES * $FRAMERATE" | bc)

  # Choose a random frame.
  FRAME=$(shuf -i 1-$FRAMES -n 1)
  TIMESTAMP=$(frame_to_timestamp $FRAME $FRAMERATE)

  print_if_verbose 1 "$VIDEO contains $FRAMES frame(s) at the rate of $FRAMERATE FPS. Selected frame #$FRAME"

  echo $TIMESTAMP
}

TIMESTAMP=$(get_timestamp $VIDEO)
FRAMERATE=$(get_framerate $(get_metadata $VIDEO))
FRAME=$(timestamp_to_frame $TIMESTAMP $FRAMERATE)
print_if_verbose 1 "Using frame #$FRAME, at timestamp $TIMESTAMP."

if [[ -z $FRAME ]]
then
  echo "Unable to select a random frame. Exiting."
  exit 5
fi

if [[ $KEEP_FRAME -eq 0 ]]; then
	FRAME_FILE="$FRAME_LOCATION/temp_$FRAME"
else
	FRAME_FILE="$FRAME_LOCATION/$FRAME.png"
fi

ffmpeg -ss $TIMESTAMP -i $VIDEO  -vframes 1 -f image2 $FRAME_FILE 2> /dev/null

if [[ ! -f $FRAME_FILE ]]
then
  print_if_verbose 2 "Could not save file to $FRAME_FILE"
  exit 1
fi

print_if_verbose 2 "Frame saved to $FRAME_FILE."

feh --bg-scale $FRAME_FILE
print_if_verbose 2 "Replaced wallpaer."

# Remove frame, unless specified otherwise.
if [[ $KEEP_FRAME -eq 0 ]]; then
	rm $FRAME_FILE
	print_if_verbose 2 "Removed frame file."
fi

